package tech

import "ssp/set"

func (s *Solve) Medusa(g *Grid) (used bool, results []*Result) {
	tags := new([9][9][9]int)
	g.initTags(tags)
	color := 2
	stronglinks := g.findStrongLinks()

	// Optimized loop structure:
	// 1. Cache cell reference to avoid repeated grid lookups
	// 2. Iterate only over actual candidates instead of all 9 possible
	for rowNum := range 9 {
		for colNum := range 9 {
			cell := g.rows[rowNum].cells[colNum] // Cache cell reference
			if cell.solved() {
				continue
			}
			// Iterate only over candidates that exist in the cell (instead of 0-8)
			for _, cand := range cell.cands.GetList() {
				if tags[rowNum][colNum][cand] == 1 {
					tags[rowNum][colNum][cand] = color
					g.medusaFlag(cell, cand, color, stronglinks, tags) // Use cached cell
					color += 2
				}
			}
		}
	}

	ruleFuns := []func(*[9][9][9]int) (bool, []*Result){
		//g.medusaRule1,
		//g.medusaRule2,
		//g.medusaRule6,
		//g.medusaRule3,
		//g.medusaRule4,
		g.medusaRule5,
	}
	for _, f := range ruleFuns {
		if ok, rs := f(tags); ok {
			used = true
			results = append(results, rs...)
		}
	}
	return
}

func (g *Grid) medusaRule1(tags *[9][9][9]int) (used bool, results []*Result) {
	for rowNum := range 9 {
		for colNum := range 9 {
			colorCands := map[int][]int{}
			for _, cand := range g.rows[rowNum].cells[colNum].cands.GetList() {
				colorCands[tags[rowNum][colNum][cand]] = append(colorCands[tags[rowNum][colNum][cand]], cand)
			}
			for color, cands := range colorCands {
				if len(cands) > 1 {
					//fmt.Println(color, rowNum, colNum, cands)
					result := new(Result)
					g.canRemovedMedusa(color, tags, result)
					result.addHelpCells([]*Cell{g.rows[rowNum].cells[colNum]})
					result.setStrategyName("Medusa Rule 1")
					results = append(results, result)
					used = true
				}

			}
		}
	}
	return
}

func (g *Grid) medusaRule2(tags *[9][9][9]int) (used bool, results []*Result) {
	for _, house := range append(g.rows[:], append(g.cols[:], g.boxes[:]...)...) {
		if ok, rs := g.medusaRule2House(tags, house); ok {
			used = true
			results = append(results, rs...)
		}
	}
	return
}

func (g *Grid) medusaRule2House(tags *[9][9][9]int, house House) (used bool, results []*Result) {
	colorCandCells := map[[2]int][]*Cell{}
	for _, cell := range house.cells {
		for _, cand := range cell.cands.GetList() {
			colorCandCells[[2]int{tags[cell.rowNum][cell.colNum][cand], cand}] = append(
				colorCandCells[[2]int{tags[cell.rowNum][cell.colNum][cand], cand}], cell)
		}
	}
	for colorCand, cells := range colorCandCells {
		if len(cells) > 1 {
			color := colorCand[0]
			//cand := colorCand[1]
			//fmt.Println(color, cand, cells)
			result := new(Result)
			g.canRemovedMedusa(color, tags, result)
			result.addHelpCells(cells)
			result.setStrategyName("Medusa Rule 2")
			results = append(results, result)
			used = true
		}
	}
	return
}

func (g *Grid) medusaRule3(tags *[9][9][9]int) (used bool, results []*Result) {
	for rowNum := range 9 {
		for colNum := range 9 {
			if g.rows[rowNum].cells[colNum].solved() || g.rows[rowNum].cells[colNum].cands.Count() < 3 {
				continue
			}
			for _, cands := range combine(g.rows[rowNum].cells[colNum].cands.GetList(), 2) {
				color1 := tags[rowNum][colNum][cands[0]]
				color2 := tags[rowNum][colNum][cands[1]]
				if color1/2 == color2/2 && color1 != color2 {
					//fmt.Println(color1, color2, rowNum, colNum, cands)
					result := new(Result)
					result.addRemoved(set.NewFull().Diff(set.NewSet(cands...)),
						[]*Cell{g.rows[rowNum].cells[colNum]})
					g.addHelpCandsMedusa(color1, color2, tags, result)
					result.addHelpCells([]*Cell{g.rows[rowNum].cells[colNum]})
					result.setStrategyName("Medusa Rule 3")
					results = append(results, result)
					used = true
					break
				}
			}
		}
	}
	return
}

func (g *Grid) medusaRule4(tags *[9][9][9]int) (used bool, results []*Result) {
	colorCandCells := map[[2]int][]*Cell{}
	for rowNum := range 9 {
		for colNum := range 9 {
			if g.rows[rowNum].cells[colNum].solved() {
				continue
			}
			for _, cand := range g.rows[rowNum].cells[colNum].cands.GetList() {
				color := tags[rowNum][colNum][cand] / 2 * 2
				key := [2]int{color, cand}
				colorCandCells[key] = append(colorCandCells[key], g.rows[rowNum].cells[colNum])
			}
		}
	}

	for colorCand, cells := range colorCandCells {
		//color := colorCand[0]
		cand := colorCand[1]
		for _, pair := range combine(cells, 2) {
			//if isSameHouse(pair) {
			//	continue
			//}
			color1 := tags[pair[0].rowNum][pair[0].colNum][cand]
			color2 := tags[pair[1].rowNum][pair[1].colNum][cand]
			if color1 == color2 {
				continue
			}
			if ok, rmCells := g.canRemoveSeenCands(set.NewSet(cand), pair); ok {
				//fmt.Println(color1, color2, cand, pair)
				result := new(Result)
				result.addRemoved(set.NewSet(cand), rmCells)
				g.addHelpCandsMedusa(color1, color2, tags, result)
				result.addHelpCells(pair)
				result.setStrategyName("Medusa Rule 4")
				results = append(results, result)
				used = true
			}
		}
	}

	return
}

func (g *Grid) medusaRule5(tags *[9][9][9]int) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() || cell.cands.Count() < 3 {
				continue
			}
			for _, cand1 := range cell.cands.GetList() {
				found := false
				for _, cand2 := range cell.cands.GetList() {
					if cand1 == cand2 {
						continue
					}
					color := tags[cell.rowNum][cell.colNum][cand2]
					anotherColor := color + 1 - color%2*2
					for _, seen := range g.seenCells(cell) {
						if tags[seen.rowNum][seen.colNum][cand1] == anotherColor {
							result := new(Result)
							result.addRemoved(set.NewSet(cand1), []*Cell{cell})
							g.addHelpCandsMedusa(color, anotherColor, tags, result)
							result.addHelpCells([]*Cell{cell, seen})
							result.setStrategyName("Medusa Rule 5")
							results = append(results, result)
							used = true
							found = true
							break
						}
					}
					if found {
						break
					}
				}
			}
		}
	}
	return
}

func (g *Grid) medusaRule6(tags *[9][9][9]int) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() {
				continue
			}
			cand0 := cell.cands.GetList()[0]
			seens := g.seenCells(cell)
			for _, seen := range seens {
				if !seen.cands.Has(cand0) {
					continue
				}
				color := tags[seen.rowNum][seen.colNum][cand0]
				if color == tags[cell.rowNum][cell.colNum][cand0] {
					continue
				}

				coverCells := []*Cell{seen}
				cover := true
				for _, cand := range cell.cands.GetList()[1:] {
					can := false
					for _, seen := range seens {
						if seen.cands.Has(cand) && tags[seen.rowNum][seen.colNum][cand] == color {
							can = true
							coverCells = append(coverCells, seen)
							break
						}
					}
					if !can {
						cover = false
						break
					}
				}
				if !cover {
					continue
				}

				result := new(Result)
				g.canRemovedMedusa(color, tags, result)
				result.addHelpCells([]*Cell{cell})
				result.addHelpCells(coverCells)
				result.setStrategyName("Medusa Rule 6")
				results = append(results, result)
				used = true
				break
			}
		}
	}

	return
}

func (g *Grid) addHelpCandsMedusa(color1, color2 int, tags *[9][9][9]int, result *Result) {
	g1 := result.getHelpCandsGroup()
	g2 := result.getHelpCandsGroup()
	for rowNum := range 9 {
		for colNum := range 9 {
			if g.rows[rowNum].cells[colNum].solved() {
				continue
			}
			for cand := range 9 {
				if tags[rowNum][colNum][cand] == color1 {
					g1.add(set.NewSet(cand), []*Cell{g.rows[rowNum].cells[colNum]})
				} else if tags[rowNum][colNum][cand] == color2 {
					g2.add(set.NewSet(cand), []*Cell{g.rows[rowNum].cells[colNum]})
				}
			}
		}
	}
}

func (g *Grid) canRemovedMedusa(color int, tags *[9][9][9]int, result *Result) {
	anotherColor := color + 1 - color%2*2
	g1 := result.getHelpCandsGroup()
	for rowNum := range 9 {
		for colNum := range 9 {
			for cand := range 9 {
				if tags[rowNum][colNum][cand] == color {
					result.addRemoved(set.NewSet(cand), []*Cell{g.rows[rowNum].cells[colNum]})
				} else if tags[rowNum][colNum][cand] == anotherColor {
					g1.add(set.NewSet(cand), []*Cell{g.rows[rowNum].cells[colNum]})
				}
			}
		}
	}
}

func (g *Grid) medusaFlag(start *Cell, cand int, color int, stronglinks map[[3]int][]*Cell, tags *[9][9][9]int) {
	anotherColor := color + 1 - color%2*2
	cells := stronglinks[[3]int{start.rowNum, start.colNum, cand}]
	for _, cell := range cells {
		if tags[cell.rowNum][cell.colNum][cand] == 1 {
			tags[cell.rowNum][cell.colNum][cand] = anotherColor
			g.medusaFlag(cell, cand, anotherColor, stronglinks, tags)
		}
	}
	if start.cands.Count() == 2 {
		for anotherCand, t := range tags[start.rowNum][start.colNum] {
			if t == 1 {
				tags[start.rowNum][start.colNum][anotherCand] = anotherColor
				g.medusaFlag(start, anotherCand, anotherColor, stronglinks, tags)
			}
		}
	}
}

func (g *Grid) initTags(tags *[9][9][9]int) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() {
				continue
			}
			for _, cand := range cell.cands.GetList() {
				tags[cell.rowNum][cell.colNum][cand] = 1
			}
		}
	}
}
